﻿using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
using MongoDB.Driver;
using Sustainalytics.Entities;
using Sustainalytics.ScreeningTool.Synchronization.Handlers;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace Sustainalytics.ScreeningTool.Synchronization.Processors
{
    public class ProcessorWithGrouping<T> : IProcessor where T : IHasCompanyId
    {
        private readonly IMongoCollection<T> _collection;
        private readonly IHandlerWithGrouping<T> _handler;

        public ProcessorWithGrouping(IMongoCollection<T> collection, IHandlerWithGrouping<T> handler)
        {
            _collection = collection;
            _handler = handler;
        }

        public string CollectionName => _collection.CollectionNamespace.FullName;

        public async Task ForEachExtraElementsAsync(Func<IEnumerable<ExtraElement>, Guid, Task> processExtraElements)
        {
            await _collection
                .Aggregate(new AggregateOptions { AllowDiskUse = true })
                .Match(GetFilter())
                .Project<T>(GetProjection())
                .Group(GetGroupByCompanyId())
                .ForEachAsync(async result =>
                {
                    var extraElements = _handler.GetExtraElements(result.Values);
                    await processExtraElements.Invoke(extraElements, result.CompanyId).ConfigureAwait(false);
                }).ConfigureAwait(false);
        }

        protected virtual FilterDefinition<T> GetFilter()
            => Builders<T>.Filter.Empty;

        private ProjectionDefinition<T> GetProjection()
            => _handler.GetProjection(Builders<T>.Projection).Include(x => x.CompanyId);

        private static ProjectionDefinition<T, AggregateResult<T>> GetGroupByCompanyId()
            => new BsonDocument { { IdField, "$CompanyId" }, { ValuesField, new BsonDocument("$push", "$$ROOT") } };

        private const string IdField = "_id";
        private const string ValuesField = "Values";

        public class AggregateResult<TEntity>
        {
            [BsonElement(IdField)]
            public Guid CompanyId { get; set; }

            [BsonElement(ValuesField)]
            public IEnumerable<TEntity> Values { get; set; }
        }
    }
}
